package com.coffee.netty.connection;

import com.coffee.concurrent.CoffeeScheduler;
import com.coffee.netty.util.RemotingUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.AttributeKey;
import java.util.concurrent.ScheduledFuture;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import lombok.*;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * client connect server info
 * @author songkui
 * @since 2024/7/31
 */
@Getter
@Setter
@ToString(of={ "id", "serverId", "host", "port",  "onlineType",  "channel"})
@EqualsAndHashCode(of = {"serverId", "channel"})
public class Connection {
    private static final InternalLogger logger = InternalLoggerFactory.getInstance(CoffeeConnectionFactory.class);

    private long id;  //userid or customize
    private int serverId;  //connect server  id
    private String host;
    private int port;
    private int timeOut; //connect time out
    private OnlineType onlineType;
    private Channel channel;

    /** Attribute key for connection */
    public static final AttributeKey<Connection> CONNECTION = AttributeKey.valueOf("connection");

    private AtomicBoolean closed  = new AtomicBoolean(false); // close state

    /* the reference count used for this connection. If equals 2, it means this connection has been referenced 2 times */
//    private final AtomicInteger  referenceCount   = new AtomicInteger();

    /**  Disconnect Reconnect */
    private ScheduledFuture<?> reconnectSchedule;
    private int reconnectTime;
    private boolean reconnect; //true reconnect;  false: not reconnect;
    private ConnectionType connectionType; // reconnect state

    public Connection(long id, String ip, int port, int serverId){
        this.id = id;
        this.host = ip;
        this.port = port;
        this.serverId = serverId;
        this.onlineType = OnlineType.Unbound;
        this.connectionType = ConnectionType.InitialConnect;
    }
    public Connection ofReconnect(boolean reconnect, int reconnectTime){
        this.reconnect = reconnect;
        this.reconnectTime = reconnectTime;
        return this;
    }

    public void successConnect(Channel channel){
        this.channel = channel;
        this.channel.attr(CONNECTION).set(this);
        this.onlineType = OnlineType.Online;
        CoffeeScheduler.cancelScheduled(reconnectSchedule);
        reconnectSchedule = null;
    }



    /**
     * to check whether the connection is fine to use
     *
     * @return true if connection is fine
     */
    public boolean isFine() {
        return this.channel != null && this.channel.isActive();
    }

    public boolean isStrongFine(){
        return this.channel != null && this.channel.isActive() && onlineType == OnlineType.Online;
    }

    public static Connection getByChannel(Channel channel){
        return channel.attr(Connection.CONNECTION).get();
    }


    public void writeAndFlush(Object msg){
        this.channel.writeAndFlush(msg);
    }

    /**
     * Close the connection.
     */
    public void close() {
        if (closed.compareAndSet(false, true)) {
            this.setOnlineType(OnlineType.OffLine);
            try {
                if (this.getChannel() != null) {
                    this.getChannel().close().addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (logger.isInfoEnabled()) {
                                logger.info("Close the connection to remote address={}, result={}, cause={}",
                                        RemotingUtil.parseRemoteAddress(Connection.this.getChannel()), future.isSuccess(), future.cause());
                            }
                        }

                    });
                }
            } catch (Exception e) {
                logger.warn("Exception  when closing connection {}", RemotingUtil.parseRemoteAddress(Connection.this.getChannel()), e);
            }
        }
    }

}
